package dataMode;


public abstract class QRData {
	
	protected int errorCorrectionLevel;
	protected int version;
	protected String data;
	protected String binaryData;
	protected int mode;
	protected int versionCapacity;
	protected int tmpResult;
	protected int bitStringSize;
	
	public QRData(String data, int version, int errorCorrectionLevel) {
		this.data = data;
		this.version = version;
		this.errorCorrectionLevel = errorCorrectionLevel;
		this.versionCapacity = this.getVersionCapacity();
		this.binaryData = new String();
	}
	
	protected abstract void encode();
	
	public void encodeDataToBinary() {
		this.binaryData += getBinaryMode(); // Mode
		this.binaryData += getBinaryLenghtData(); // Taille de la chaine

		
		encode();

		completeBitData();
		padBitData();
		addBitWords();
	}
	
	/** Retourne la taille de la chaine de caractère sous forme de chaine binaire
	 * 
	 * @return
	 */
	
	protected String getBinaryLenghtData() {
		String binaryLenght;
		
		// On récupère le nombre de bits sur lequel doit être codé la longueur
		int lenghtData = this.getSizeBitsLenght();

		// On génère la chaine binaire contenant la taille de la chaine data
		binaryLenght = Integer.toBinaryString(data.length());

		// On rajoute les zéros pour arriver au nombre de bits souhaité
		return addZeroOnLeft(binaryLenght, lenghtData);
	}
	
	/**  Complete la chaine binaire avec des zeros pour determiner
	 * la fin de la chaine si necessaire */
	protected void completeBitData() {
		int marge = this.versionCapacity - this.binaryData.length();
		
		if(marge >= 4) {
			this.binaryData += "0000";
		}
		else {
			for(int i=0; i<marge; i++) {
				this.binaryData += "0"; 
			}
		}
	}
	
	/** Complete la chaine binaire avec des zéros jusqu'à arriver à un multiple de 8 */
	protected void padBitData() {
		while(this.binaryData.length() % 8 != 0) {
			this.binaryData += "0";
		}
	}
	
	/** Complète la chaine avec des zéros jusqu'à la taille souhaitée
	 * 
	 * @param data
	 * @param bitSize
	 * @return chaine de taille bitSize
	 */
	protected String addZeroOnLeft(String data, int bitSize) {
		StringBuffer dataBuffer = new StringBuffer(data);
		int i, lenghtData = data.length();
		
		for(i = 0; i < bitSize-lenghtData; i++) {
			dataBuffer.insert(0,"0");
		}
		
		return dataBuffer.toString();
	}
	
	/** Finalise la chaine binaire en ajoutant des mots spéciaux de 8 bits
	 * si la chaine jusqu'à ce que la chaine atteigne sa capacité maximale */
	protected void addBitWords() {
		boolean altern = true;
		
		while(this.binaryData.length() < this.versionCapacity) {
			if(altern) {
				this.binaryData += QRData.SPECIAL_BIT_STRING1;
			}
			else {
				this.binaryData += QRData.SPECIAL_BIT_STRING2;
			}
			altern = !altern;
		}
	}
	
	/** Retourne la valeur décimale d'un caractère
	 * 
	 * @param c
	 * @return decimalValue
	 */
	protected int getCode(char c) {
		if (c >= '0' && c <= '9') {
	        return c - '0';
	    } 
	    else if (c >= 'A' && c <= 'Z') {
	        return c - 'A' + 10;
	    } 
	    else {
	        switch (c) {
	            case ' ' : return 36;
	            case '$' : return 37;
	            case '%' : return 38;
	            case '*' : return 39;
	            case '+' : return 40;
	            case '-' : return 41;
	            case '.' : return 42;
	            case '/' : return 43;
	            case ':' : return 44;
	            default : throw new IllegalArgumentException("illegal char :" + c);
	        }
	    }
	}	
	

	/*
	 * Retourne la chaine binaire correspondant au mode du QRCode
	*/
	public String getBinaryMode(){
		return addZeroOnLeft(Integer.toBinaryString(this.mode), 4);
	}
	
	public void addRsCodeBlocks(String rsCode){
		this.binaryData += rsCode;
	}
	
	
	//Getteurs sur les variables de QRData
	public String getBinaryData() {
		return this.binaryData;
	}
	
	public int getVersion() {
		return this.version;
	}
	
	public int getMode() {
		return this.mode;
	}
	
	public int getErrorCorrectionLevel() {
		return this.errorCorrectionLevel;
	}
		
	
	public final static int ERROR_CORR_LVL_L = 1;
	public final static int ERROR_CORR_LVL_M = 2;
	public final static int ERROR_CORR_LVL_Q = 3;
	public final static int ERROR_CORR_LVL_H = 4;
	
	public final static int MODE_NUMERIC = 1;
	public final static int MODE_ALPHANUMERIC = 2;
	public final static int MODE_BINARY = 4;
	public final static int MODE_KANJI = 8;
	
	public final static String SPECIAL_BIT_STRING1 = "11101100";
	public final static String SPECIAL_BIT_STRING2 = "00010001";
	
	/**
	 * Retourne le nombre de bits total qu'il faut pour créer la chaine binaire
	 * @param version
	 * @param errorCorrectionLevel
	 * @return
	 */
	public int getVersionCapacity() {
		return versionCapacities[this.version-1][this.errorCorrectionLevel-1];
	}
	
	public final static int[][] versionCapacities = {
		{152,128,104,72}, // Version 1
		{272,224,176,128},
		{440,352,272,208},
		{640,512,384,288},
		{864,688,496,368},
		{1088,864,608,480},
		{1248,992,704,528},
		{1552,1232,880,688},
		{1856,1456,1056,800},
		{2192,1728,1232,976}, // Version 10
		{2592,2032,1440,1120},
		{2960,2320,1648,1264},
		{3424,2672,1952,1440},
		{3688,2920,2088,1576},
		{4184,3320,2360,1784},
		{4712,3624,2600,2024},
		{5176,4056,2936,2264},
		{5768,4504,3176,2504},
		{6360,5016,3560,2728},
		{6888,5352,3880,3080}, // Version 20
		{7456,5712,4096,3248},
		{8048,6256,4544,3536},
		{8752,6880,4912,3712},
		{9392,7312,5312,4112},
		{10208,8000,5744,4304},
		{10960,8496,6032,4768},
		{11744,9024,6464,5024},
		{12248,9544,6968,5288},
		{13048,10136,7288,5608},
		{13880,10984,7880,5960}, // Version 30
		{14744,11640,8264,6344},
		{15640,12328,8920,6760},
		{16568,13048,9368,7208},
		{17528,13800,9848,7688},
		{18448,14496,10288,7888},
		{19472,15312,10832,8432},
		{20528,15936,11408,8768},
		{21616,16816,12016,9136},
		{22496,17728,12656,9776},
		{23648,18672,13328,10208} // Version 40
	};

	/**
	 * Retourne le nombre de bits nécessaire à l'encodage en binaire des données
	 * @param version
	 * @param mode
	 * @return
	 */
	public int getSizeBitsLenght() {
		int nbBits; 
		// On récupère le nombres de bits en fonction de la version et du mode
		if(this.version >= 1 && this.version <= 9) {
			switch(this.mode) {
				case QRData.MODE_NUMERIC: nbBits = 10;
					break;
				case QRData.MODE_ALPHANUMERIC: nbBits = 9;
					break;
				case QRData.MODE_BINARY: nbBits = 8;
					break;
				case QRData.MODE_KANJI: nbBits = 8;
					break;
				default: throw new IllegalArgumentException("Mode non valide : "+this.mode);
			}
		}
		else if(this.version >= 10 && this.version <= 26) {
			switch(this.mode) {
				case QRData.MODE_NUMERIC: nbBits = 12;
					break;
				case QRData.MODE_ALPHANUMERIC: nbBits = 11;
					break;
				case QRData.MODE_BINARY: nbBits = 16;
					break;
				case QRData.MODE_KANJI: nbBits = 10;
					break;
				default: throw new IllegalArgumentException("Mode non valide : "+this.mode);
			}
		}
		else if(this.version >= 27 && this.version <= 40) {
			switch(this.mode) {
				case QRData.MODE_NUMERIC: nbBits = 14;
					break;
				case QRData.MODE_ALPHANUMERIC: nbBits = 13;
					break;
				case QRData.MODE_BINARY: nbBits = 16;
					break;
				case QRData.MODE_KANJI: nbBits = 12;
					break;
				default: throw new IllegalArgumentException("Mode non valide : "+this.mode);
			}
		}
		else {
			throw new IllegalArgumentException("Numero de version non valide : "+this.version);
		}
		
		return nbBits;
	}
}
